﻿using System;
using System.Drawing;
using System.IO;
using System.Windows.Forms;

namespace Elements
{
    public partial class DigitsRecog : Form
    {
        readonly int[, ,] sample = new int[10, 2, 25];
        readonly double[, ,] Dsample = new double[10, 2, 25];
        private int model, m_num, count, algorithm = -1;
        private bool isSaved;
        private bool isDraged;
        readonly int[,] pos = new int[2, 500];

        public DigitsRecog()
        {
            InitializeComponent();
        }

        #region 事件

        //载入样本
        private void loadTempToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (algorithm == -1)
            {
                MessageBox.Show("请先选择识别算法", "信息提示");
            }
            else
            {
                load();
                lblMessage.Text = "按下鼠标左键并拖动，在右边输入板中手写数字";
            }
        }

        //新建样本
        private void createTempToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (algorithm == -1)
            {
                MessageBox.Show("请先选择识别算法", "信息提示");
            }
            else
            {
                lblMessage.Text = "按下鼠标左键并拖动，在右边输入板中手写数字，" + "\n" + "依次输入0~9数字样本";
                btnOCR.Enabled = false;
                btnClean.Enabled = true;
                btnStudy.Enabled = true;
                cboClass.Enabled = true;
                isSaved = false;
                picWritingPad.Refresh();
                var g = picWritingPad.CreateGraphics();
                g.Clear(Color.White);
            }
        }

        //保存样本
        private void saveTempToolStripMenuItem_Click(object sender, EventArgs e)
        {
            var saveFileDialog = new SaveFileDialog { Filter = "文件（*.dat;）|*.dat|All File(*.*)|*.*" };
            if (saveFileDialog.ShowDialog() == DialogResult.OK)
            {
                var str = saveFileDialog.FileName;
                save(str);
            }
        }

        //退出
        private void existToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Close();
        }

        //识别
        private void btnOCR_Click(object sender, EventArgs e)
        {
            if (algorithm == 0)
            {
                txtOCRText.Text = " " + recognize();
            }
            else if (algorithm == 1)
            {
                txtOCRText.Text = " " + twoValueBayes();
            }
            else if (algorithm == 2)
            {
                txtOCRText.Text = " " + LMSE();
            }
            else if (algorithm == 3)
            {
                txtOCRText.Text = " " + reward_Punish();
            }
        }

        //清除
        private void btnClean_Click(object sender, EventArgs e)
        {
            clearall();
        }

        //训练
        private void btnStudy_Click(object sender, EventArgs e)
        {
            if (algorithm == 0 || algorithm == 1)
            {
                var tem = character(pos);
                for (int j = 0; j < 25; j++)
                {
                    sample[model, m_num, j] = tem[j];
                }
            }
            else if (algorithm == 2 || algorithm == 3)
            {
                var tem = CHARACTER(pos);
                for (int j = 0; j < 25; j++)
                {
                    Dsample[model, m_num, j] = tem[j];
                }
            }
            lblMessage.Text = "数字" + model + " 的第" + (m_num + 1) + " 个样本已完成训练.\n每个数字输入两个样本.";
            m_num++;
            m_num = m_num % 2;
            if (model == 9) btnOCR.Enabled = true;
            clearall();
        }

        //鼠标左键按下事件
        private void picWritingPad_MouseDown(object sender, MouseEventArgs e)
        {
            pos[0, count] = e.X;
            pos[1, count] = e.Y;
            count++;
            isDraged = true;
        }

        //鼠标左键按住移动事件
        private void picWritingPad_MouseMove(object sender, MouseEventArgs e)
        {
            if (isDraged)
            {
                pos[0, count] = e.X;
                pos[1, count] = e.Y;
                count++;

                picWritingPad.Refresh();
                var pen = new Pen(Color.Black, 2.0f);
                var g = picWritingPad.CreateGraphics();

                for (int i = 0; i < count - 1; i++)
                {
                    if (pos[0, i + 1] == -1)
                    {
                        i++; continue;
                    }
                    g.DrawLine(pen, pos[0, i], pos[1, i], pos[0, i + 1], pos[1, i + 1]);
                }
            }

        }

        //鼠标左键松开事件
        private void picWritingPad_MouseUp(object sender, MouseEventArgs e)
        {
            pos[0, count] = -1;
            pos[1, count] = -1;
            count++;
            isDraged = false;
        }

        //识别类别选择
        private void cboClass_SelectedIndexChanged(object sender, EventArgs e)
        {
            algorithm = cboClass.SelectedIndex;
        }

        //数字选择
        private void cboDigits_SelectedIndexChanged(object sender, EventArgs e)
        {
            model = cboDigits.SelectedIndex;
        }

        //关闭窗口
        private void DigitsRecog_FormClosed(object sender, FormClosedEventArgs e)
        {
            if (isSaved == false)
            {
                const string s = "是否要保存已修改的样本到样本文件？";
                var result = MessageBox.Show(s, "信息提示", MessageBoxButtons.YesNo, MessageBoxIcon.Information);
                if (result == DialogResult.Yes)
                {
                    var saeFileDialog = new SaveFileDialog { Filter = "文件（*.dat;）|*.dat|All File(*.*)|*.*" };
                    if (saeFileDialog.ShowDialog() == DialogResult.OK)
                    {
                        var str = saeFileDialog.FileName;
                        save(str);
                    }
                }
            }
        }

        #endregion

        #region 基本方法

        /// <summary>
        /// 载入数据
        /// </summary>
        void load()
        {
            var open = new OpenFileDialog { Filter = "文件（*.dat;）|*.dat|All File(*.*)|*.*" };

            if (open.ShowDialog() == DialogResult.OK)
            {
                var inputStream = File.OpenRead(open.FileName);
                var reader = new BinaryReader(inputStream);

                if (algorithm == 0 || algorithm == 1)
                {
                    for (int k = 0; k < 10; k++)
                    {
                        for (int i = 0; i < 2; i++)
                        {
                            for (int j = 0; j < 25; j++)
                            {
                                sample[k, i, j] = reader.ReadInt32();
                            }
                        }
                    }
                }
                else if (algorithm == 2 || algorithm == 3)
                {
                    for (int k = 0; k < 10; k++)
                    {
                        for (int i = 0; i < 2; i++)
                        {
                            for (int j = 0; j < 25; j++)
                            {
                                Dsample[k, i, j] = reader.ReadDouble();
                            }
                        }
                    }
                }

                reader.Close();
            }

            btnOCR.Enabled = true;
            btnClean.Enabled = true;
            btnStudy.Enabled = true;
            cboClass.Enabled = true;
        }

        /// <summary>
        /// 存储数据
        /// </summary>
        /// <param name="name">保存文件名称</param>
        void save(string name)
        {
            var outputStream = File.Create(name);
            var wr = new BinaryWriter(outputStream);
            if (algorithm == 0 || algorithm == 1)
            {
                for (int k = 0; k < 10; k++)
                {
                    for (int i = 0; i < 2; i++)
                    {
                        for (int j = 0; j < 25; j++)
                        {
                            wr.Write(sample[k, i, j]);
                        }
                    }
                }
            }
            else if (algorithm == 2 || algorithm == 3)
            {
                for (int k = 0; k < 10; k++)
                {
                    for (int i = 0; i < 2; i++)
                    {
                        for (int j = 0; j < 25; j++)
                        {
                            wr.Write(Dsample[k, i, j]);
                        }
                    }
                }
            }

            wr.Close();
            isSaved = true;
            MessageBox.Show("在当前目录存储文件", "信息提示");
        }

        void clearall()
        {
            count = 0;
            picWritingPad.Refresh();
            var g = picWritingPad.CreateGraphics();
            g.Clear(Color.White);
            txtOCRText.Text = "";
        }

        #endregion

        #region 识别方法

        /// <summary>
        /// 模板匹配算法，返回手写数字的类别
        /// </summary>
        /// <returns></returns>
        int recognize()
        {
            var distance = new int[10, 2];
            var tem = character(pos);
            var n = 0;

            for (int k = 0; k < 10; k++)
            {
                for (int i = 0; i < 2; i++)
                {
                    distance[k, i] = 0;
                    for (int j = 0; j < 25; j++)
                    {
                        distance[k, i] += Math.Abs(sample[k, i, j] - tem[j]);
                    }
                }
            }

            //计算distance[k][i]的最小者和相应的序号k
            for (int k = 1; k < 10; k++)
            {
                for (int i = 0; i < 2; i++)
                {
                    if (distance[k, i] < distance[n, i])
                        n = k;
                }
            }

            return n;
        }

        /// <summary>
        /// 二值Bayes算法
        /// </summary>
        /// <returns>返回手写数字的类别</returns>
        int twoValueBayes()
        {
            var Pw = new double[10];//先验概率P(wj)=Nj/N
            var P = new double[10, 25];//Pj(wi)(wi:wi类,j:第j个特征)
            var PXw = new double[10];//类条件概率P(X|wj)
            var PwX = new double[10];//后验概率P(wj|X)

            int i, j, k;
            //求先验概率
            var testsample = character(pos);

            for (i = 0; i < 10; i++)
            {
                Pw[i] = 1.0 / 10.0;
            }

            //求类条件概率
            for (k = 0; k < 10; k++)
            {
                for (j = 0; j < 25; j++)
                {
                    int numof1 = 0;
                    for (i = 0; i < 2; i++)
                    {
                        numof1 += sample[k, i, j] > 0 ? 1 : 0;
                    }
                    P[k, j] = numof1 + 1;
                }
            }

            //求类条件概率
            for (i = 0; i < 10; i++)
            {
                double p = 1;
                for (j = 0; j < 25; j++)
                {
                    p *= (testsample[j] > 0) ? P[i, j] : 1;
                }
                PXw[i] = p;
            }

            //求后验概率
            double PX = 0.0, maxP = 0.0;
            int number = 0;
            for (i = 0; i < 10; i++)
            {
                PX += PXw[i];
            }
            for (i = 0; i < 10; i++)
            {
                PwX[i] = PXw[i] / PX;
                if (PwX[i] > maxP)
                {
                    maxP = PwX[i];
                    number = i;
                }
            }

            return number;
        }

        /// <summary>
        /// LMSE算法
        /// </summary>
        /// <returns>返回手写数字的类别</returns>
        int LMSE()
        {
            var w = new double[10, 26];
            var d = new double[10];
            var x = new double[26];
            bool flag;
            int n, i;

            var testsample = CHARACTER(pos);

            //权值初值为0
            for (n = 0; n < 10; n++)
            {
                for (i = 0; i < 26; i++)
                {
                    w[n, i] = 0;
                }
            }
            int c = 0, cc = 1;
            do
            {
                flag = true;
                for (n = 0; n < 10; n++)
                {
                    for (i = 0; i < 2; i++)
                    {
                        //取已知样品
                        int j;
                        for (j = 0; j < 25; j++)
                        {
                            x[j] = Dsample[n, i, j];
                        }
                        x[25] = 1;

                        //计算结果权矢量
                        for (j = 0; j < 10; j++)
                        {
                            d[j] = 0;
                        }

                        int k;
                        for (j = 0; j < 10; j++)
                        {
                            for (k = 0; k < 26; k++)
                            {
                                d[j] += w[j, k] * x[k];
                            }
                        }
                        bool f = true;

                        //d[n]是否最大值
                        for (j = 0; j < 10; j++)
                        {
                            if (j != n)
                            {
                                f &= (d[n] > d[j]);
                            }
                        }

                        flag = f;

                        for (j = 0; j < 10; j++)
                        {
                            double rX = j == n ? 1.0 : 0.0;

                            for (k = 0; k < 26; k++)
                            {
                                w[j, k] += x[k] * (rX - d[j]) / cc;
                            }
                        }
                        ++cc;
                    }
                }
                if (++c > 2000) break;
            } while (!flag);

            var hx = new double[10];
            var num = new double[26];

            //取未知样品
            for (i = 0; i < 25; i++)
            {
                num[i] = testsample[i];
            }
            num[25] = 1;

            //计算判别函数
            for (n = 0; n < 10; n++)
            {
                hx[n] = 0;
                for (i = 0; i < 26; i++)
                {
                    hx[n] += w[n, i] * num[i];
                }
            }

            //判别函数最大值
            double maxval = hx[0];
            int number = 0;
            for (n = 1; n < 10; n++)
            {
                if (hx[n] > maxval)
                {
                    maxval = hx[n];
                    number = n;
                }
            }

            return number;
        }

        /// <summary>
        /// 奖惩算法
        /// </summary>
        /// <returns>返回手写数字的类别</returns>
        int reward_Punish()
        {
            var w = new double[10, 26];//权值
            var d = new double[10];//迭代计算结果权矢量
            var x = new double[26];//增1样品
            var hx = new double[10];//判别函数
            bool flag;
            int n, i;
            int c = 0;//迭代次数

            var testsample = CHARACTER(pos);

            //权值初值为0
            for (n = 0; n < 10; n++)
            {
                for (i = 0; i < 26; i++)
                {
                    w[n, i] = 0;
                }
            }

            do
            {
                flag = true;
                for (n = 0; n < 10; n++)
                {
                    for (i = 0; i < 2; i++)
                    {
                        //取已知样品
                        int j;
                        for (j = 0; j < 25; j++)
                        {
                            x[j] = Dsample[n, i, j];
                        }
                        x[25] = 1;//取最后一位置1

                        for (j = 0; j < 10; j++)
                        {
                            d[j] = 0;
                        }
                        int k;
                        for (j = 0; j < 10; j++)
                        {
                            for (k = 0; k < 26; k++)
                            {
                                d[j] += w[j, k] * x[k];
                            }
                        }
                        bool f = true;

                        //判断d[n]是否为最大值
                        for (j = 0; j < 10; j++)
                        {
                            if (j != n)
                            {
                                f &= (d[n] > d[j]);
                            }
                        }

                        if (!f)
                        {
                            for (j = 0; j < 10; j++)
                            {
                                for (k = 0; k < 26; k++)
                                {
                                    if (j == n)
                                    {
                                        w[j, k] += x[k];
                                    }
                                    else if (d[j] > d[n])
                                    {
                                        w[j, k] -= x[k];
                                    }
                                }
                            }
                            flag = false;
                        }
                    }
                }
                if (++c > 2000) break;
            } while (!flag);

            var num = new double[26];//未知样品
            for (i = 0; i < 25; i++)
            {
                num[i] = testsample[i];
            }

            num[25] = 1;//末位置1
            for (n = 0; n < 10; n++)
            {
                hx[n] = 0;
                for (i = 0; i < 26; i++)
                {
                    hx[n] += w[n, i] * num[i];
                }
            }

            var maxval = hx[0];
            int number = 0;
            for (n = 1; n < 10; n++)
            {
                if (hx[n] > maxval)
                {
                    maxval = hx[n];
                    number = n;
                }
            }

            return number;
        }

        /// <summary>
        /// 标准化，将输入的数字缩放到5X5标准大小
        /// </summary>
        /// <param name="temp">模板</param>
        /// <returns>标准化结果</returns>
        int[] character(int[,] temp)
        {
            int minx = 1000, miny = 1000, maxx = 0, maxy = 0;
            var chr = new int[25];

            //分别计算最小和最大的x[i],y[i]
            for (int i = 0; i < count; i++)
            {
                if (temp[0, i] == -1) continue;
                if (temp[0, i] < minx) minx = temp[0, i];
                if (temp[1, i] < miny) miny = temp[1, i];
                if (temp[0, i] > maxx) maxx = temp[0, i];
                if (temp[1, i] > maxy) maxy = temp[1, i];
            }
            var xlen = maxx - minx;
            var ylen = maxy - miny;

            //按比例缩放到标准大小，并计算row[i],col[i]
            for (int i = 0; i < count; i++)
            {
                if (temp[0, i] == -1) continue;
                int ti = (temp[1, i] - miny) * 5 / ylen;
                int tj = (temp[0, i] - minx) * 5 / xlen;
                if (ti == 5) ti = 4;
                if (tj == 5) tj = 4;

                chr[ti * 5 + tj]++;
            }

            return chr;
        }

        /// <summary>
        /// 标准化，将输入的数字缩放到5X5标准大小
        /// </summary>
        /// <param name="temp">模板</param>
        /// <returns>标准化结果</returns>
        double[] CHARACTER(int[,] temp)
        {
            int minx = 1000, miny = 1000, maxx = 0, maxy = 0;
            var chr = new int[25];
            var t = new double[25];

            //分别计算最小和最大的x[i],y[i]
            for (int i = 0; i < count; i++)
            {
                if (temp[0, i] == -1) continue;
                if (temp[0, i] < minx) minx = temp[0, i];
                if (temp[1, i] < miny) miny = temp[1, i];
                if (temp[0, i] > maxx) maxx = temp[0, i];
                if (temp[1, i] > maxy) maxy = temp[1, i];
            }
            var xlen = maxx - minx;
            var ylen = maxy - miny;

            //按比例缩放到标准大小，并计算row[i],col[i]
            for (int i = 0; i < count; i++)
            {
                if (temp[0, i] == -1) continue;
                int ti = (temp[1, i] - miny) * 5 / ylen;
                int tj = (temp[0, i] - minx) * 5 / xlen;
                if (ti == 5) ti = 4;
                if (tj == 5) tj = 4;

                chr[ti * 5 + tj]++;
            }

            var w = xlen / 5;
            var h = ylen / 5;
            for (int i = 0; i < 5; i++)
            {
                for (int j = 0; j < 5; j++)
                {
                    t[i * 5 + j] = chr[i * 5 + j] * 7.0 / (w * h);
                }
            }
            return t;
        }

        #endregion
    }
}
